<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    
    <title>理解Python装饰器(Decorator) | 王鹏的技术博客</title>
    
    
        <meta property="og:site_name" content="王鹏的技术博客">
    
    
        <meta property="article:author" content="王 鹏">
    
    
        <link rel="icon" href="/mhexo/img/favicon.ico">
    
    
<link rel="stylesheet" href="/mhexo/css/minireset.min.css">

    
<link rel="stylesheet" href="/mhexo/css/all.min.css">

    
<link rel="stylesheet" href="/mhexo/css/csshake.min.css">

    
<link rel="stylesheet" href="/mhexo/css/hljs/lioshi.css">

    
<link rel="stylesheet" href="/mhexo/css/jquery.fancybox.min.css">

    
<link rel="stylesheet" href="/mhexo/styl/main.css">

    
<script src="/mhexo/js/jquery.min.js"></script>

    
<script src="/mhexo/js/highlight.min.js"></script>

    
<script src="/mhexo/js/jquery.fancybox.min.js"></script>

    
<script src="/mhexo/js/clipboard.min.js"></script>

<meta name="generator" content="Hexo 5.2.0"></head>
<body>
    <header>
  <div class="outer">
    <div class="inner">
      <h1 class="logo-wrap">
        <a>王鹏的技术博客<b><sup>1.1</sup></b></a>
      </h1>
    </div>
    <div class="inner">
      <nav class="main-nav">
        
          <a href="/mhexo/">首页</a>
        
          <a href="/mhexo/archives">归档</a>
        
          <a href="/mhexo/categories">分类</a>
        
          <a href="/mhexo/tags">标签</a>
        
          <a href="/mhexo/friends">友链</a>
        
          <a href="/mhexo/about">关于</a>
        
          <a target="_blank" rel="noopener" href="https://www.baidu.com">百度</a>
        
      </nav>
    </div>
  </div>
</header>
    <div class="content">
        <section class="outer">
    <article>
        <div class="article-title">
    <h2>
        <a href="/mhexo/2020/12/21/%E7%90%86%E8%A7%A3Python%E8%A3%85%E9%A5%B0%E5%99%A8(Decorator)/" class="shake shake-little" title="理解Python装饰器(Decorator)">
            
            理解Python装饰器(Decorator)
        </a>
    </h2>
    <div class="meta_box">
    
        
        
            
                
        
        <div class="meta meta_auth">
            <img src="/mhexo/img/default.png" alt="head" />
            <p>anonymous</p>
        </div>
    
        
        
        <div class="meta meta_date">
            <i class="fas fa-calendar-alt fa-fw" aria-hidden="true"></i>
            <p>发布于：2020年12月21日</p>
        </div>
    
        <div class="meta meta_update">
            <i class="fas fa-edit fa-fw" aria-hidden="true"></i>
            <p>更新于：2020年12月21日</p>
        </div>
    </div>
    

</div>

        <hr>
        <div class="article-entry">
            
            
            
            <h2 id="理解Python装饰器-Decorator"><a href="#理解Python装饰器-Decorator" class="headerlink" title="理解Python装饰器(Decorator)"></a>理解Python装饰器(Decorator)</h2><p>Python装饰器看起来类似Java中的注解，然鹅和注解并不相同，不过同样能够实现面向切面编程。</p>
<p>想要理解Python中的装饰器，不得不先理解闭包（closure）这一概念。</p>
<h3 id="闭包"><a href="#闭包" class="headerlink" title="闭包"></a>闭包</h3><blockquote>
<h3 id="1、概念"><a href="#1、概念" class="headerlink" title="1、概念"></a>1、概念</h3><p><strong>闭包函数：</strong>声明在一个函数中的函数，叫做闭包函数。</p>
<p><strong>闭包：</strong>内部函数总是可以访问其所在的外部函数中声明的参数和变量，即使在其外部函数被返回（寿命终结）了之后。</p>
<h3 id="2、特点"><a href="#2、特点" class="headerlink" title="2、特点"></a>2、特点</h3><p> 让外部访问函数内部变量成为可能；</p>
<p> 局部变量会常驻在内存中；</p>
<p> 可以避免使用全局变量，防止全局变量污染；</p>
<p> 会造成内存泄漏（有一块内存空间被长期占用，而不被释放）</p>
<h3 id="3、闭包的创建：­­­"><a href="#3、闭包的创建：­­­" class="headerlink" title="3、闭包的创建：­­­"></a>3、<strong>闭包的创建：</strong>­­­</h3><p>闭包就是可以创建一个独立的环境，每个闭包里面的环境都是独立的，互不干扰。闭包会发生内存泄漏，<strong>每次外部函数执行的时 候，外部函数的引用地址不同，都会重新创建一个新的地址。</strong>但凡是当前活动对象中有被内部子集引用的数据，那么这个时候，这个数据不删除，保留一根指针给内部活动对象。</p>
<p>闭包内存泄漏为： key = value，key 被删除了 value 常驻内存中; 局部变量闭包升级版（中间引用的变量） =&gt; 自由变量；</p>
</blockquote>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># print_msg是外围函数</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">print_msg</span>():</span></span><br><span class="line">    msg = <span class="string">&quot;I&#x27;m closure&quot;</span></span><br><span class="line"></span><br><span class="line">    <span class="comment"># printer是嵌套函数</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">printer</span>():</span></span><br><span class="line">        print(msg)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> printer</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 这里获得的就是一个闭包</span></span><br><span class="line">closure = print_msg()</span><br><span class="line"><span class="comment"># 输出 I&#x27;m closure</span></span><br><span class="line">closure()</span><br></pre></td></tr></table></figure>

<p><code>msg</code>是一个局部变量，在<code>print_msg</code>函数执行之后应该就不会存在了。但是嵌套函数引用了这个变量，将这个局部变量封闭在了嵌套函数中，这样就形成了一个闭包。</p>
<p>结合这个例子再看维基百科的解释，就清晰明了多了。闭包就是引用了自有变量的函数，这个函数保存了执行的上下文，可以脱离原本的作用域独立存在。</p>
<p>下面来看看Python中的装饰器。</p>
<h3 id="装饰器"><a href="#装饰器" class="headerlink" title="装饰器"></a>装饰器</h3><p>一个普通的装饰器一般是这样：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> functools</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">log</span>(<span class="params">func</span>):</span></span><br><span class="line"><span class="meta">    @functools.wraps(func)</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">wrapper</span>(<span class="params">*args, **kwargs</span>):</span></span><br><span class="line">        print(<span class="string">&#x27;call %s():&#x27;</span> % func.__name__)</span><br><span class="line">        print(<span class="string">&#x27;args = &#123;&#125;&#x27;</span>.<span class="built_in">format</span>(*args))</span><br><span class="line">        <span class="keyword">return</span> func(*args, **kwargs)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> wrapper</span><br></pre></td></tr></table></figure>

<p>这样就定义了一个打印出方法名及其参数的装饰器。</p>
<p>调用之：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@log</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">test</span>(<span class="params">p</span>):</span></span><br><span class="line">    print(test.__name__ + <span class="string">&quot; param: &quot;</span> + p)</span><br><span class="line">    </span><br><span class="line">test(<span class="string">&quot;I&#x27;m a param&quot;</span>)</span><br></pre></td></tr></table></figure>

<p>输出：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">call test():</span><br><span class="line">args = I<span class="string">&#x27;m a param</span></span><br><span class="line"><span class="string">test param: I&#x27;</span>m a param</span><br></pre></td></tr></table></figure>

<p>装饰器在使用时，用了<code>@</code>语法，让人有些困扰。其实，装饰器只是个方法，与下面的调用方式没有区别：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">test</span>(<span class="params">p</span>):</span></span><br><span class="line">    print(test.__name__ + <span class="string">&quot; param: &quot;</span> + p)</span><br><span class="line"></span><br><span class="line">wrapper = log(test)</span><br><span class="line">wrapper(<span class="string">&quot;I&#x27;m a param&quot;</span>)</span><br></pre></td></tr></table></figure>

<p><code>@</code>语法只是将函数传入装饰器函数，并无神奇之处。</p>
<p>值得注意的是<code>@functools.wraps(func)</code>，这是python提供的装饰器。它能把原函数的元信息拷贝到装饰器里面的 func 函数中。函数的元信息包括docstring、<strong>name</strong>、参数列表等等。可以尝试去除<code>@functools.wraps(func)</code>，你会发现<code>test.__name__</code>的输出变成了wrapper。</p>
<h3 id="带参数的装饰器"><a href="#带参数的装饰器" class="headerlink" title="带参数的装饰器"></a>带参数的装饰器</h3><p>装饰器允许传入参数，一个携带了参数的装饰器将有三层函数，如下所示：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> functools</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">log_with_param</span>(<span class="params">text</span>):</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">decorator</span>(<span class="params">func</span>):</span></span><br><span class="line"><span class="meta">        @functools.wraps(func)</span></span><br><span class="line">        <span class="function"><span class="keyword">def</span> <span class="title">wrapper</span>(<span class="params">*args, **kwargs</span>):</span></span><br><span class="line">            print(<span class="string">&#x27;call %s():&#x27;</span> % func.__name__)</span><br><span class="line">            print(<span class="string">&#x27;args = &#123;&#125;&#x27;</span>.<span class="built_in">format</span>(*args))</span><br><span class="line">            print(<span class="string">&#x27;log_param = &#123;&#125;&#x27;</span>.<span class="built_in">format</span>(text))</span><br><span class="line">            <span class="keyword">return</span> func(*args, **kwargs)</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> wrapper</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> decorator</span><br><span class="line">    </span><br><span class="line"><span class="meta">@log_with_param(&quot;param&quot;)</span></span><br><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">test_with_param</span>(<span class="params">p</span>):</span></span><br><span class="line">    print(test_with_param.__name__)</span><br></pre></td></tr></table></figure>

<p>看到这个代码是不是又有些疑问，内层的decorator函数的参数func是怎么传进去的？和上面一般的装饰器不大一样啊。</p>
<p>其实道理是一样的，将其<code>@</code>语法去除，恢复函数调用的形式一看就明白了：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 传入装饰器的参数，并接收返回的decorator函数</span></span><br><span class="line">decorator = log_with_param(<span class="string">&quot;param&quot;</span>)</span><br><span class="line"><span class="comment"># 传入test_with_param函数</span></span><br><span class="line">wrapper = decorator(test_with_param)</span><br><span class="line"><span class="comment"># 调用装饰器函数</span></span><br><span class="line">wrapper(<span class="string">&quot;I&#x27;m a param&quot;</span>)</span><br></pre></td></tr></table></figure>

<p>输出结果与正常使用装饰器相同：</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">call test_with_param():</span><br><span class="line">args = I<span class="string">&#x27;m a param</span></span><br><span class="line"><span class="string">log_param = param</span></span><br><span class="line"><span class="string">test_with_param</span></span><br></pre></td></tr></table></figure>


<p>转载至：<a target="_blank" rel="noopener" href="https://www.jianshu.com/p/ee82b941772a">https://www.jianshu.com/p/ee82b941772a</a></p>

        </div>
        <div class="article-copyright">
            
    <blockquote>
        <p>
            版权声明：本文为「子曰无衣」的原创文章，博客内容遵循 署名-非商业性使用-相同方式共享 协议。<br>本文永久链接是：https://wangpdizhi.gitee.io/mhexo/2020/12/21/%E7%90%86%E8%A7%A3Python%E8%A3%85%E9%A5%B0%E5%99%A8(Decorator)/
        </p>
    </blockquote>


        </div>
    </article>
    

    <section id="toc-div" >
        <ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#%E7%90%86%E8%A7%A3Python%E8%A3%85%E9%A5%B0%E5%99%A8-Decorator"><span class="toc-text">理解Python装饰器(Decorator)</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#%E9%97%AD%E5%8C%85"><span class="toc-text">闭包</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#1%E3%80%81%E6%A6%82%E5%BF%B5"><span class="toc-text">1、概念</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#2%E3%80%81%E7%89%B9%E7%82%B9"><span class="toc-text">2、特点</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#3%E3%80%81%E9%97%AD%E5%8C%85%E7%9A%84%E5%88%9B%E5%BB%BA%EF%BC%9A%C2%AD%C2%AD%C2%AD"><span class="toc-text">3、闭包的创建：­­­</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E8%A3%85%E9%A5%B0%E5%99%A8"><span class="toc-text">装饰器</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#%E5%B8%A6%E5%8F%82%E6%95%B0%E7%9A%84%E8%A3%85%E9%A5%B0%E5%99%A8"><span class="toc-text">带参数的装饰器</span></a></li></ol></li></ol>
    </section>
    <section id="gohome" style="display: none;"><a>顶</a></section>

    <script>
        function get_top_by_link(link){
            var hnid = "#" + $(link).attr("data");
            if ($(hnid).length > 0){
                return $(hnid).offset().top;
            }else{
                return 0;
            }
        }
        //go to hn
        function gotohn(link){
            $("html,body").animate({scrollTop: get_top_by_link(link) }, 300);
        }
        //页面滚动
        function update(){
            var scrollH = $(window).scrollTop();
            if($(".toc-link")){
                $(".toc-link").each(function(i,link){
                    var mdHeight = get_top_by_link(link);
                    if(mdHeight <= scrollH + 40){
                        //高亮导航菜单
                        $('.toc-link').removeClass('on');
                        $(link).addClass('on');
                    }
                });
            }
            //返回顶部显隐
            if(scrollH < 200){
                $("#gohome").css("display","none");
            }else{
                $("#gohome").css("display","block");
            }
        }
        $(function(){
            //修复部分锚点从属关系
            if($("#toc-div >li").length > 0){
                $("#toc-div >li").appendTo($("#toc-div >ol:first"));
            }
            //返回顶部
            $('#gohome').click(function(){
                $("html,body").animate({scrollTop: 0}, 300);
                return false;
            })
            //遍历锚点
            $(".toc-link").each(function(i,link){
                $(link).attr("data",$(link).attr('href').substring(1));
                $(link).attr("href","javascript:void(0);");
                $(link).attr("onclick","gotohn(this);");
            })
            //绑定滚动事件
            $(window).bind('scroll', update);
            //初始化toc
            var first_toc = $(".toc-link")[0];
            var first_scroll = get_top_by_link(first_toc);
            var window_scroll = $(window).scrollTop();
            if(window_scroll <= first_scroll){
                $(first_toc).addClass('on');
            }
        })
    </script>

</section>
    </div>
    <footer>
    <div class="outer">
        <div class="inner">
            Powered by <a href="http://prowiki.demopage.icu/" target="_blank">ProWiki</a>
            &copy;2020 子曰无衣<br>
            <a target="_blank" rel="noopener" href="http://beian.miit.gov.cn/">冀ICP备00000000号</a>
        </div>
    </div>
</footer>

<script src="/mhexo/js/custom.js"></script>


    <script>onload_content();</script>

</body>
</html>
